#pragma once

#include <functional>
#include <map>
#include <string>
#include <iostream>

#include <boost/any.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/random_generator.hpp>


template <typename _ReturnArgs = void>
class MessageBus
{
public:
	// regist message
	template< class... _Args, class _Func, class = typename std::enable_if<!std::is_member_function_pointer<_Func>::value>::type>
	void Attach(std::string key, _Func && func)
	{
		std::function<_ReturnArgs(_Args...)> fn = [&](_Args... args) { return func(std::forward<_Args>(args)...); };
		m_map.insert(std::make_pair(key, std::move(fn)));
	}

	// non-const member function 
	template<class... _Args, class _Class, class... _DeclareArgs, class _Object>
	void Attach(std::string key, _ReturnArgs(_Class::*func)(_DeclareArgs...), _Object && object)
	{
		std::function<_ReturnArgs(_Args...)> fn = [&, func](_Args... args) { return (*object.*func)(std::forward<_Args>(args)...); };
		m_map.insert(std::make_pair(key, std::move(fn)));
	}

	// const member function 
	template<class... _Args, class _Class, class... _DeclareArgs, class _Object>
	void Attach(std::string key, _ReturnArgs(_Class::*func)(_DeclareArgs...) const, _Object && object)
	{
		std::function<_ReturnArgs(_Args...)> fn = [&, func](_Args... args) { return (*object.*func)(std::forward<_Args>(args)...); };
		m_map.insert(std::make_pair(key, std::move(fn)));
	}

	// Broadcast messages, call all the callback functions
	template<typename... _Args>
	void SendReq(std::string key, _Args... args)
	{
		auto range = m_map.equal_range(key);
		for (auto it = range.first; it != range.second; it++)
		{
			std::function<_ReturnArgs(_Args...)> func = boost::any_cast<std::function<_ReturnArgs(_Args...)>>(it->second);
			func(args...);
		}
	}

	// remove message
	template<typename... _Args>
	void Remove(std::string key)
	{
		auto range = m_map.equal_range(key);
		m_map.erase(range.first, range.second);
	}

    // get random uuid key
	template<typename... _Args>
	const std::string RandomKey() const
	{
		boost::uuids::uuid uuid = boost::uuids::random_generator()();
		return std::move(boost::lexical_cast<std::string>(uuid));
	}

public:
	MessageBus() = default;
	~MessageBus() = default;
private:
	MessageBus(const MessageBus&) = delete;
	MessageBus& operator=(const MessageBus&) = delete;

	std::multimap<std::string, boost::any> m_map;
};